﻿using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Web;

namespace HttpHelper
{
    /// <summary>
    /// 请求类型
    /// </summary>
    public enum Method
    {
        /// <summary>
        /// GET请求
        /// </summary>
        GET,

        /// <summary>
        ///
        /// </summary>
        POST,

        /// <summary>
        ///
        /// </summary>
        HEAD,

        /// <summary>
        ///
        /// </summary>
        PUT,

        /// <summary>
        ///
        /// </summary>
        OPTIONS,

        /// <summary>
        ///
        /// </summary>
        DELETE,

        /// <summary>
        ///
        /// </summary>
        TRACE,

        /// <summary>
        ///
        /// </summary>
        CONNECT
    }

    /// <summary>
    /// Http 请求类
    /// </summary>
    public class WinHttp : RequestData
    {
        /// <summary>
        /// Http 请求类 无惨构造函数
        /// </summary>
        public WinHttp() : base(new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase), new CookieEasy())
        {
        }

        /// <summary>
        /// Http 请求类 有惨构造函数
        /// </summary>
        /// <param name="client">Http 请求全局对象</param>
        public WinHttp(RequestData client) : base(client.DefaultHeaders, client.Cookie)
        {
            AutoCookies = client.AutoCookies;//Cookie 是否处理 默认处理
            NeverRedirect = client.NeverRedirect;//是否跳转 默认不跳转
            RedirectIndex = client.RedirectIndex;//跳转次数 默认 20 次
            ReadWriteTimeout = client.ReadWriteTimeout;//上传下载超时时间
            Timeout = client.Timeout;//请求超时 时间 默认五秒
            UserProxy = client.UserProxy; // 代理对象 默认为空
            Auto = client.Auto;// 是否补全 协议头 默认补全
            Certificate = client.Certificate;//默认证书
        }

        #region 请求头 Header 相关设置

        /// <summary>
        /// 此次请求所携带的协议头
        /// </summary>
        public NameValueCollection Headers { get; } = new NameValueCollection();

        /// <summary>
        /// 获取 Header Value
        /// </summary>
        /// <param name="header"></param>
        /// <returns></returns>
        public override string GetHeader(string header)
        {
            return Headers.Get(header) ?? string.Empty;
        }

        /// <summary>
        /// 添加或替换HTTP协议头（必须在Open后使用）
        /// </summary>
        /// <param name="header">协议头名称</param>
        /// <param name="value">协议头内容 空文本为删除此协议头</param>
        public override void SetRequestHeader(string header, string value = null)
        {
            if (string.IsNullOrEmpty(header)) return;
            // 判断Value 是否为空 如为空  则删除 Header
            if (string.IsNullOrEmpty(value)) Headers.Remove(header);
            //如不为空 则 添加或更新Header 值Value
            else Headers[header] = value;
        }

        /// <summary>
        /// 初始化 Headers
        /// </summary>
        private void SetRequestHeaders()
        {
            Headers.Clear();//清除原有协议头
            if (Auto)//设置默认协议头
            {
                Headers.Add(HeaderKey.UserAgent, HeaderValue.UserAgent);
                Headers.Add(HeaderKey.Accept, HeaderValue.Accept);
                Headers.Add(HeaderKey.AcceptLanguage, HeaderValue.AcceptLanguage);
                Headers.Add(HeaderKey.CacheControl, HeaderValue.CacheControl);
            }
            foreach (var item in DefaultHeaders)
                Headers[item.Key] = item.Value;//添加固定协议头
        }

        #endregion 请求头 Header 相关设置

        #region 请求方式 请求地址 提交数据

        /// <summary>
        /// 提交数据格式
        /// </summary>
        private string contentType;

        /// <summary>
        /// 提交数据
        /// </summary>
        public byte[] Data { get; set; }

        /// <summary>
        /// 储存请求模式 GET POST 之类的
        /// </summary>
        public Method Method { get; set; }

        /// <summary>
        /// 请求地址URL
        /// </summary>
        public Uri Url { get; set; }

        /// <summary>
        /// 记录执行请求的 方法 跟 地址
        /// </summary>
        /// <param name="method">HTTP请求的方法 例： POST、GET、HEAD 之类的</param>
        /// <param name="url">http://www.nixsw.com/</param>
        /// <param name="query"></param>
        /// <returns>创建成功 返回 true 创建失败 返回 false (Url 格式不正确)</returns>
        public bool Open(Method method, string url, Dictionary<string, string> query = null)
        {
            //URL 格式是否正则
            if (!url.StartsWith("http")) url = $"http://{url}";
            //尝试以绝对路径 的方式 创建Uri  创建失败返回false
            if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) return false;
            return Open(method, uri, query);
        }

        /// <summary>
        /// 记录执行请求的 方法 跟 地址
        /// </summary>
        /// <param name="method">HTTP请求的方法 例： POST、GET、HEAD 之类的</param>
        /// <param name="uri">Uri 地址 http://www.nixsw.com/ </param>
        /// <param name="query"></param>
        /// <returns>创建成功 返回 true 创建失败 返回 false (Url 格式不正确)</returns>
        public bool Open(Method method, Uri uri, Dictionary<string, string> query = null)
        {
            if (query?.Count > 0)
            {
                string url = uri.AbsoluteUri + (uri.Query.Length > 0 ? string.Empty : "?") + GteForm(query);
                Url = new Uri(url);
            }
            else Url = uri;//创建成功为 Url 赋值
            Referer = Url.AbsoluteUri;
            Method = method;//保存请求方式
            SetRequestHeaders();//初始化 header
            Data = null;//默认提交数据为空
            contentType = "application/x-www-form-urlencoded";//默认提交数据格式
            return true;//返回成功 True
        }

        /// <summary>
        /// 字典To表单
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static string GteForm(Dictionary<string, string> data)
        {
            var form = data.Select(a => $"{HttpUtility.UrlEncode(a.Key)}={HttpUtility.UrlEncode(a.Value)}");
            //使用&连接符  连接数组
            return string.Join("&", form);
        }

        /// <summary>
        ///提交JSON 类型数据
        /// </summary>
        /// <typeparam name="T">需转换的数据类型</typeparam>
        /// <param name="data">需要提交数据内容</param>
        /// <param name="encoding">提交数据编码</param>
        /// <returns>成功返回响应数据</returns>
        public HttpResponse Send<T>(T data, Encoding encoding = null)
            where T : class
        {
            var json = JsonConvert.SerializeObject(data);
            ContentType = "application/json";// $"application/json; charset={encoding.BodyName}"
            return Send(json, encoding);
        }

        /// <summary>
        /// 提交表单
        /// </summary>
        /// <param name="data">需要提交的表单内容</param>
        /// <param name="encoding">提交数据编码</param>
        /// <returns>成功返回响应数据</returns>
        public HttpResponse Send(Dictionary<string, string> data, Encoding encoding = null)
        {
            //FormUrlEncodedContent 会请求地址超出长度的
            //将data中的每个元素投影到新表单。
            return Send(GteForm(data), encoding);
        }

        /// <summary>
        /// 提交文本内容
        /// </summary>
        /// <param name="data">提交数据</param>
        /// <param name="encoding">提交数据编码</param>
        /// <returns>成功返回响应数据</returns>
        public HttpResponse Send(string data, Encoding encoding = null)
        {
            // 如果为空 默认为UTF8
            if (encoding == null)
                encoding = Encoding.UTF8;
            // 设置默认提交数据格式 如果 Content-Type 协议头已存在则不会修改数据
            contentType = $"{contentType}; charset={encoding.BodyName}";
            return Send(encoding.GetBytes(data));
        }

        /// <summary>
        /// 执行提交字节集数据
        /// </summary>
        /// <param name="data"> 提交数据</param>
        /// <returns>成功返回响应数据</returns>
        public HttpResponse Send(byte[] data = null)
        {
            //判断是否为post
            if (Method == Method.POST)
            {
                Data = data;//为提交数据赋值
                //尝试添加 提交类型协议头  如不存在 则进行添加 如已存在 则不进行任何有改
                if (string.IsNullOrEmpty(Headers.Get(HeaderKey.ContentType)))
                {
                    Headers.Add(HeaderKey.ContentType, contentType);
                }
            }
            return DoMethod(this);
        }

        #endregion 请求方式 请求地址 提交数据

        #region 执行静态类

        /// <summary>
        /// 一些默认设置
        /// </summary>
        /// <param name="Url"></param>
        /// <returns></returns>
        private static HttpWebRequest DefaultSetting(Uri Url)
        {
            var request = (HttpWebRequest)WebRequest.Create(Url);
            request.AllowWriteStreamBuffering = false;//禁止缓冲加快载入速度
            request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; //设置压缩
                                                                                                       //  X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
                                                                                                       //request.ClientCertificates
            return request;
        }

        /// <summary>
        /// 执行请求 Send 请求
        /// </summary>
        /// <param name="data">提交的数据</param>
        /// <returns>成功返回响应数据</returns>
        private static HttpResponse DoMethod(WinHttp data)
        {
            HttpWebRequest request;
            try
            {
                request = SetRequest(data);
            }
            catch (Exception ex)
            {
                return new HttpResponse() { Message = ex.Message };
            }
            try
            {
                using HttpWebResponse Response = (HttpWebResponse)request.GetResponse();
                return GetData(data, Response);
            }
            catch (WebException ex)
            {
                HttpResponse Response;
                if (ex.Response != null)
                {
                    using HttpWebResponse WebResponse = (HttpWebResponse)ex.Response;
                    Response = GetData(data, WebResponse);
                }
                else Response = new HttpResponse();
                Response.Message = ex.Message;
                return Response;
            }
            catch (Exception ex)
            {
                return new HttpResponse() { Message = ex.Message };
            }
            finally
            {
                if (request != null)
                {
                    request.Abort();
                }
            }
        }

        /// <summary>
        /// 生成返回数据
        /// </summary>
        /// <param name="data"></param>
        /// <param name="response"></param>
        /// <returns></returns>
        private static HttpResponse GetData(WinHttp data, HttpWebResponse response)
        {
            var Result = new HttpResponse();
            //如果参数回空 返回空对象
            if (response == null) return Result;

            #region 响应状态 响应 Header

            Result.Status = response.StatusCode;//相应状态
            Result.StatusText = response.StatusDescription;//相应状态文本
            Result.Headers = response.Headers;//相应Headers

            #endregion 响应状态 响应 Header

            #region 数据类型 数据编码

            int Start = response.ContentType.IndexOf(";");
            if (Start > 0)
            {
                Result.ContentType = response.ContentType[..Start];
                Result.Encoding = Encoding.GetEncoding(response.CharacterSet);
            }
            else Result.ContentType = response.ContentType;

            #endregion 数据类型 数据编码

            #region 取相应正文

            using (var stream = new MemoryStream())
            {
                response.GetResponseStream().CopyTo(stream, 10240);
                Result.Body = stream.ToArray();
            }
            Result.ContentLength = Result.Body.Length;

            #endregion 取相应正文

            #region 检查更新Cookie

            data.Cookie.UpH(Result.Headers);

            #endregion 检查更新Cookie

            return Result;
        }

        /// <summary>
        /// 证书设置
        /// </summary>
        /// <param name="data"></param>
        /// <param name="request"></param>
        private static void SetCer(HttpWebRequest request, WinHttp data)
        {
            //证书不为空 为请求添加证书
            if (data.Certificate != null) request.ClientCertificates.Add(data.Certificate);
        }

        /// <summary>
        /// 设置请求方法 提交数
        /// </summary>
        /// <param name="request">请求对象</param>
        /// <param name="method">请求放到发</param>
        /// <param name="data">提交数据</param>
        private static void SetMethod(HttpWebRequest request, Method method, byte[] data)
        {
            request.Method = method.ToString();//设置请求方法
            if (data != null)
            {//设置提交数据
                request.ContentLength = data.Length;
                request.GetRequestStream().Write(data, 0, data.Length);
            }
            else
                request.ContentLength = 0;
        }

        /// <summary>
        /// 设置一些提交设置
        /// </summary>
        /// <param name="data">提交配置</param>
        /// <returns></returns>
        private static HttpWebRequest SetRequest(WinHttp data)
        {
            var request = DefaultSetting(data.Url);
            // 设置证书
            SetCer(request, data);
            if (data.Timeout > 0) //设置请求超时
                request.Timeout = data.Timeout;
            //if (string.IsNullOrEmpty(data.Headers.Get(HeaderKey.)))
            request.KeepAlive = data.KeepAlive;
            if (data.ReadWriteTimeout > 0) //设置上传下载超时
                request.ReadWriteTimeout = data.ReadWriteTimeout;
            request.AllowAutoRedirect = data.NeverRedirect;// 是否重定向
            // 重定向次数
            if (data.RedirectIndex > 0)
                request.MaximumAutomaticRedirections = data.RedirectIndex;
            request.Proxy = data.UserProxy;//设置代理
            //设置Header
            request.Headers.SetHeaders(data.Headers);
            // request.Headers.Set(item.Key, item.Value);
            //设置Cookie
            if (data.AutoCookies)
                request.Headers.Add(HeaderKey.Cookie, data.Cookie.Cookies);

            SetMethod(request, data.Method, data.Data);
            return request;
        }

        #endregion 执行静态类
    }

    internal static class WebHeaders
    {
        public static void SetHeaders(this WebHeaderCollection header, NameValueCollection name)
        {
            var property = typeof(WebHeaderCollection).GetProperty("InnerCollection",
                BindingFlags.Instance | BindingFlags.NonPublic);
            if (property?.GetValue(header) is NameValueCollection collection)
            {
                collection.Add(name);
            }
        }
    }
}